AWS PrivateLinkを使って別AWSアカウントにあるEC2にHTTP通信してみた
AWS PrivateLinkを使って別AWSアカウントにあるEC2にHTTP通信させる設定をやったことが無かったなと思いやってみました。
AWS PrivateLinkって何
AWSサービスやVPC内でホストされているアプリケーションをプライベートに利用できるようにするサービスです。
AWS PrivateLinkを利用することでパブリックな通信を行わずにAWSサービス (VPCエンドポイントに対応していれば) などを使用することができます。
AWS PrivateLink および VPC エンドポイント
構成
ざっくりやりたいことは以下の1~3になります。
- アカウントBでNLBとEC2を作成
- アカウントAでVPCエンドポイントとEC2を作成
- アカウントAのEC2からVPCエンドポイントへHTTPアクセス
- 構成図は今回メインとなる部分を抜粋して記載しています。
- EC2への接続はSession Managerを使用するのでインターフェイス型VPCエンドポイントを作成します。
- EC2ではAmazon Linux2を使用してWebサーバーにApacheを使用します。
- こちらのドキュメントに記載されている通りAmazon Linux2のリポジトリはS3にあるのでゲートウェイ型VPCエンドポイントを作成します。
設定
アカウントBでAWSリソース作成
AWSリソースはCloudFormationで作成します。
まずはアカウントBでリソースを作成していきます。
VPCエンドポイントをアカウントAで作成できるようにするためアカウントBではVPCエンドポイントサービスというものを作成しています。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Description: Service Stack Metadata: # ------------------------------------------------------------# # Metadata # ------------------------------------------------------------# AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Parameters for VPC Parameters: - VPCCIDR - Label: default: Parameters for Subnet Parameters: - PrivateSubnet01CIDR - Label: default: Parameters for VPC Endpoint Parameters: - AWSAccountId - Label: default: Parameters for EC2 Parameters: - EC2VolumeSize - EC2VolumeIOPS - EC2AMI - EC2InstanceType Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VPCCIDR: Default: 10.0.0.0/16 Type: String PrivateSubnet01CIDR: Default: 10.0.0.0/24 Type: String AWSAccountId: Type: String Description: AWS AccountId EC2VolumeSize: Default: 32 Type: Number EC2VolumeIOPS: Default: 3000 Type: Number EC2AMI: Default: ami-0bba69335379e17f8 Type: AWS::EC2::Image::Id EC2InstanceType: Default: t3.micro Type: String Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: account-b-vpc # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PrivateSubnet01: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PrivateSubnet01CIDR Tags: - Key: Name Value: account-b-private-01 VpcId: !Ref VPC # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: account-b-private-rtb PrivateRtAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnet01 # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# EC2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 GroupName: EC2-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - FromPort: 80 IpProtocol: tcp ToPort: 80 CidrIp: !Ref VPCCIDR Tags: - Key: Name Value: EC2-sg VpcId: !Ref VPC VPCEndpointSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for Systems Manager GroupName: SystemsManager-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourceSecurityGroupId: !Ref EC2Sg FromPort: 443 IpProtocol: tcp ToPort: 443 Tags: - Key: Name Value: SystemsManager-sg VpcId: !Ref VPC # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# S3Endpoint: Type: AWS::EC2::VPCEndpoint Properties: RouteTableIds: - !Ref PrivateRouteTable ServiceName: !Sub com.amazonaws.${AWS::Region}.s3 VpcEndpointType: Gateway VpcId: !Ref VPC SystemsManagerEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG SystemsManagerMessageEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG # ------------------------------------------------------------# # VPC Endpoint Service # ------------------------------------------------------------# VPCEndpointService: DependsOn: - NLB Type: AWS::EC2::VPCEndpointService Properties: AcceptanceRequired: true NetworkLoadBalancerArns: - !Ref NLB VPCEndpointServicePermissions: DependsOn: - VPCEndpointService Type: AWS::EC2::VPCEndpointServicePermissions Properties: AllowedPrincipals: - !Join - '' - - 'arn:aws:iam::' - !Ref AWSAccountId - ':root' ServiceId: !Ref VPCEndpointService # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# EC2IAMPolicy: Type: AWS::IAM::ManagedPolicy Properties: PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "s3:GetObject" Resource: - !Join - '' - - 'arn:aws:s3:::amazonlinux.' - !Sub ${AWS::Region} - '.amazonaws.com/*' - !Join - '' - - 'arn:aws:s3:::amazonlinux-2-repos-' - !Sub ${AWS::Region} - '/*' ManagedPolicyName: iam-repository-access-policy-ec2 EC2IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore - !Ref EC2IAMPolicy RoleName: iam-repository-access-role-ec2 Tags: - Key: Name Value: iam-repository-access-role-ec2 EC2IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: iam-repository-access-instanceprofile-ec2 Roles: - !Ref EC2IAMRole # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# EC2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: !Ref EC2VolumeIOPS VolumeSize: !Ref EC2VolumeSize VolumeType: gp3 DisableApiTermination: false IamInstanceProfile: !Ref EC2IAMInstanceProfile ImageId: !Ref EC2AMI InstanceType: !Ref EC2InstanceType NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref EC2Sg SubnetId: !Ref PrivateSubnet01 Tags: - Key: Name Value: account-b-ec2 UserData: !Base64 | #!/bin/bash yum update -y yum install httpd -y systemctl start httpd systemctl enable httpd echo "Private Link Test" > /var/www/html/index.html # ------------------------------------------------------------# # NLB # ------------------------------------------------------------# NLB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 LoadBalancerAttributes: - Key: deletion_protection.enabled Value: false Name: account-b-nlb Scheme: internal Subnets: - !Ref PrivateSubnet01 Tags: - Key: Name Value: account-b-nlb Type: network TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckEnabled: true HealthCheckIntervalSeconds: 30 HealthCheckPort: traffic-port HealthCheckProtocol: TCP HealthyThresholdCount: 5 IpAddressType: ipv4 Name: account-b-nlb-tg Port: 80 Protocol: TCP TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: true Targets: - Id: !Ref EC2 Port: 80 TargetType: instance VpcId: !Ref VPC Listener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref NLB Port: 80 Protocol: TCP
S3やSystems Managerで利用するVPCエンドポイントは153行目~184行目で作成しています。
Session Managerを利用するだけであれば以下の2つのVPCエンドポイントを作成すれば問題ありません。
- com.amazonaws.ap-northeast-1.ssm
- com.amazonaws.ap-northeast-1.ssmmessages
189行目~209行目でVPCエンドポイントサービスを作成しています。
また、アカウントAから利用できるように権限を追加しています。
デプロイは以下のコマンドを実行します。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=AWSAccountId,ParameterValue=アカウントID --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したらVPCエンドポイントサービスの画面へ移動してサービス名を確認します。
このサービス名はアカウントAでVPCエンドポイントを作成する際に使用するものになります。
アカウントAでリソース作成
アカウントAではアカウントBで作成したVPCエンドポイントサービス名を使用してVPCエンドポイントを作成します。
CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09" Description: Client Stack Metadata: # ------------------------------------------------------------# # Metadata # ------------------------------------------------------------# AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Parameters for VPC Parameters: - VPCCIDR - Label: default: Parameters for Subnet Parameters: - PrivateSubnet01CIDR - Label: default: Parameters for VPC Endpoint Parameters: - ServiceName - Label: default: Parameters for EC2 Parameters: - EC2VolumeSize - EC2VolumeIOPS - EC2AMI - EC2InstanceType Parameters: # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# VPCCIDR: Default: 10.1.0.0/16 Type: String PrivateSubnet01CIDR: Default: 10.1.0.0/24 Type: String ServiceName: Type: String EC2VolumeSize: Default: 32 Type: Number EC2VolumeIOPS: Default: 3000 Type: Number EC2AMI: Default: ami-0bba69335379e17f8 Type: AWS::EC2::Image::Id EC2InstanceType: Default: t3.micro Type: String Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: account-a-vpc # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# PrivateSubnet01: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a CidrBlock: !Ref PrivateSubnet01CIDR Tags: - Key: Name Value: account-a-private-01 VpcId: !Ref VPC # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: account-a-private-rtb PrivateRtAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnet01 # ------------------------------------------------------------# # Security Group # ------------------------------------------------------------# EC2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 GroupName: EC2-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 Tags: - Key: Name Value: EC2-sg VpcId: !Ref VPC VPCEndpointSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for Systems Manager GroupName: SystemsManager-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourceSecurityGroupId: !Ref EC2Sg FromPort: 443 IpProtocol: tcp ToPort: 443 Tags: - Key: Name Value: SystemsManager-sg VpcId: !Ref VPC PrivateLinkSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for Private Link GroupName: PrivateLink-sg SecurityGroupEgress: - CidrIp: 0.0.0.0/0 FromPort: -1 IpProtocol: -1 ToPort: -1 SecurityGroupIngress: - SourceSecurityGroupId: !Ref EC2Sg FromPort: 80 IpProtocol: tcp ToPort: 80 Tags: - Key: Name Value: PrivateLink-sg VpcId: !Ref VPC # ------------------------------------------------------------# # VPC Endpoint # ------------------------------------------------------------# SystemsManagerEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG SystemsManagerMessageEndpoint: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: true ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref VPCEndpointSG PrivateLink: Type: AWS::EC2::VPCEndpoint Properties: VpcEndpointType: Interface PrivateDnsEnabled: false ServiceName: !Ref ServiceName VpcId: !Ref VPC SubnetIds: - !Ref PrivateSubnet01 SecurityGroupIds: - !Ref PrivateLinkSG # ------------------------------------------------------------# # IAM # ------------------------------------------------------------# EC2IAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore RoleName: iam-client-access-role-ec2 Tags: - Key: Name Value: iam-client-access-role-ec2 EC2IAMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: iam-client-access-instanceprofile-ec2 Roles: - !Ref EC2IAMRole # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# EC2: Type: AWS::EC2::Instance Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: true Iops: !Ref EC2VolumeIOPS VolumeSize: !Ref EC2VolumeSize VolumeType: gp3 DisableApiTermination: false IamInstanceProfile: !Ref EC2IAMInstanceProfile ImageId: !Ref EC2AMI InstanceType: !Ref EC2InstanceType NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: 0 GroupSet: - !Ref EC2Sg SubnetId: !Ref PrivateSubnet01 Tags: - Key: Name Value: account-a-ec2
191行目から201行目でアカウントBのVPCエンドポイントサービスに対応するVPCエンドポイントを作成しています。
今回はサブネットが1つだけなので「SubnetIds」が1つになっていますが、他のサブネットからも使用したい場合は追加する必要があります。
デプロイは以下のコマンドを実行します。
こちらのCloudFormationはアカウントAで実行するものなので実行場所 (クレデンシャル) などに気を付けてください。
aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=ServiceName,ParameterValue=VPCエンドポイントサービス名 --capabilities CAPABILITY_NAMED_IAM
デプロイが完了したらアカウントBのVPCエンドポイントサービスの画面に移動してCloudFormationで作成したVPCエンドポイントサービスを選択してください。
選択したら「エンドポイント接続」タブをクリックします。
クリックすると「所有者」の欄がアカウントAのIDになった接続があるので選択します。
選択後、「アクション」から「エンドポイント接続リクエストの承諾」をクリックします。
クリックすると確認画面が出るので承諾と入力して「承諾」をクリックします。
クリック後、しばらくすると「状態欄」がAvailableになります。
ここまでで作成は完了です。
動作確認
アカウントAで作成したEC2にSession Managerで接続します。
Session Managerの利用方法は以下の公式ドキュメントをご確認ください。
セッションを開始する (Amazon EC2 コンソール)
EC2に接続できたら以下のコマンドを実行します。
VPCエンドポイントDNS名はVPCエンドポイントの詳細タブから確認できます。
curl http://VPCエンドポイントDNS名
成功すると「Private Link Test」とレスポンスが返ってきます。
アカウントBのEC2に接続してApacheのアクセスログを確認すると以下のようにアクセスされていることが確認できます。
IPアドレス「10.0.0.236」はNLBのネットワークインターフェイスのIPアドレスです。
ソースIPが確認したい場合は以下のドキュメントに記載されている通りProxy Protocolを使用する必要があります。
接続情報のプロキシプロトコルを使用する
tail -f /var/log/httpd/access_log 10.0.0.236 - - [27/Jan/2023:09:03:18 +0000] "GET / HTTP/1.1" 200 18 "-" "curl/7.79.1" 10.0.0.236 - - [27/Jan/2023:09:03:18 +0000] "GET / HTTP/1.1" 200 18 "-" "curl/7.79.1" 10.0.0.236 - - [27/Jan/2023:09:03:19 +0000] "GET / HTTP/1.1" 200 18 "-" "curl/7.79.1" 10.0.0.236 - - [27/Jan/2023:09:03:19 +0000] "GET / HTTP/1.1" 200 18 "-" "curl/7.79.1" 10.0.0.236 - - [27/Jan/2023:09:03:20 +0000] "GET / HTTP/1.1" 200 18 "-" "curl/7.79.1"
さいごに
複数アカウントにまたがると管理が複雑化するのかと思っていましたが、許可設定を入れてあげるだけで利用できるので簡単にサービスへアクセス可能になるといった感じでした。
NLBのターゲットにALBが設定できるのでパスベースルーティングなんかも利用できるのが便利な印象です。